package service

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/devops-smartbot/devops-smartbot/services/user-service/internal/auth"
	"github.com/devops-smartbot/devops-smartbot/services/user-service/internal/models"
	"github.com/devops-smartbot/devops-smartbot/services/user-service/internal/repository"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/bson"
)

type UserService struct {
	repo       *repository.UserRepository
	jwtManager *auth.JWTManager
}

func NewUserService(repo *repository.UserRepository, jwtManager *auth.JWTManager) *UserService {
	return &UserService{
		repo:       repo,
		jwtManager: jwtManager,
	}
}

// Register creates a new user
func (s *UserService) Register(ctx context.Context, username, email, password, fullName string) (*models.User, string, string, error) {
	// Check if username exists
	_, err := s.repo.GetUserByUsername(ctx, username)
	if err == nil {
		return nil, "", "", fmt.Errorf("username already exists")
	}

	// Check if email exists
	_, err = s.repo.GetUserByEmail(ctx, email)
	if err == nil {
		return nil, "", "", fmt.Errorf("email already exists")
	}

	// Hash password
	passwordHash, err := auth.HashPassword(password)
	if err != nil {
		return nil, "", "", fmt.Errorf("failed to hash password: %w", err)
	}

	// Create user
	user := &models.User{
		ID:           uuid.New().String(),
		Username:     username,
		Email:        email,
		PasswordHash: passwordHash,
		FullName:     fullName,
		Role:         models.RoleViewer, // Default role
		Status:       models.UserStatusActive,
	}

	if err := s.repo.CreateUser(ctx, user); err != nil {
		return nil, "", "", fmt.Errorf("failed to create user: %w", err)
	}

	// Generate tokens
	accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Username, user.Email, string(user.Role))
	if err != nil {
		return nil, "", "", fmt.Errorf("failed to generate access token: %w", err)
	}

	refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID)
	if err != nil {
		return nil, "", "", fmt.Errorf("failed to generate refresh token: %w", err)
	}

	// Save refresh token
	if err := s.saveRefreshToken(ctx, user.ID, refreshToken); err != nil {
		return nil, "", "", err
	}

	return user, accessToken, refreshToken, nil
}

// Login authenticates a user
func (s *UserService) Login(ctx context.Context, username, password string) (*models.User, string, string, error) {
	// Get user
	user, err := s.repo.GetUserByUsername(ctx, username)
	if err != nil {
		return nil, "", "", fmt.Errorf("invalid credentials")
	}

	// Verify password
	if err := auth.VerifyPassword(user.PasswordHash, password); err != nil {
		return nil, "", "", fmt.Errorf("invalid credentials")
	}

	// Check if user is active
	if user.Status != models.UserStatusActive {
		return nil, "", "", fmt.Errorf("user account is not active")
	}

	// Generate tokens
	accessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Username, user.Email, string(user.Role))
	if err != nil {
		return nil, "", "", fmt.Errorf("failed to generate access token: %w", err)
	}

	refreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID)
	if err != nil {
		return nil, "", "", fmt.Errorf("failed to generate refresh token: %w", err)
	}

	// Save refresh token
	if err := s.saveRefreshToken(ctx, user.ID, refreshToken); err != nil {
		return nil, "", "", err
	}

	// Update last login
	s.repo.UpdateLastLogin(ctx, user.ID)

	return user, accessToken, refreshToken, nil
}

// RefreshToken generates new tokens using refresh token
func (s *UserService) RefreshToken(ctx context.Context, refreshToken string) (string, string, error) {
	// Validate refresh token
	userID, err := s.jwtManager.ValidateRefreshToken(refreshToken)
	if err != nil {
		return "", "", fmt.Errorf("invalid refresh token: %w", err)
	}

	// Verify refresh token exists in database
	tokenHash := hashToken(refreshToken)
	storedToken, err := s.repo.GetRefreshToken(ctx, tokenHash)
	if err != nil {
		return "", "", fmt.Errorf("refresh token not found")
	}

	// Check if token is expired
	if time.Now().After(storedToken.ExpiresAt) {
		s.repo.DeleteRefreshToken(ctx, tokenHash)
		return "", "", fmt.Errorf("refresh token expired")
	}

	// Get user
	user, err := s.repo.GetUserByID(ctx, userID)
	if err != nil {
		return "", "", fmt.Errorf("user not found")
	}

	// Generate new tokens
	newAccessToken, err := s.jwtManager.GenerateAccessToken(user.ID, user.Username, user.Email, string(user.Role))
	if err != nil {
		return "", "", fmt.Errorf("failed to generate access token: %w", err)
	}

	newRefreshToken, err := s.jwtManager.GenerateRefreshToken(user.ID)
	if err != nil {
		return "", "", fmt.Errorf("failed to generate refresh token: %w", err)
	}

	// Delete old refresh token
	s.repo.DeleteRefreshToken(ctx, tokenHash)

	// Save new refresh token
	if err := s.saveRefreshToken(ctx, user.ID, newRefreshToken); err != nil {
		return "", "", err
	}

	return newAccessToken, newRefreshToken, nil
}

// Logout invalidates user's refresh tokens
func (s *UserService) Logout(ctx context.Context, userID string) error {
	return s.repo.DeleteUserRefreshTokens(ctx, userID)
}

// GetUser retrieves a user by ID
func (s *UserService) GetUser(ctx context.Context, userID string) (*models.User, error) {
	return s.repo.GetUserByID(ctx, userID)
}

// UpdateUser updates user information
func (s *UserService) UpdateUser(ctx context.Context, userID, email, fullName, avatarURL string) (*models.User, error) {
	user, err := s.repo.GetUserByID(ctx, userID)
	if err != nil {
		return nil, err
	}

	// Update fields
	if email != "" {
		user.Email = email
	}
	if fullName != "" {
		user.FullName = fullName
	}
	if avatarURL != "" {
		user.AvatarURL = avatarURL
	}

	if err := s.repo.UpdateUser(ctx, user); err != nil {
		return nil, err
	}

	return user, nil
}

// DeleteUser deletes a user
func (s *UserService) DeleteUser(ctx context.Context, userID string) error {
	// Delete refresh tokens first
	s.repo.DeleteUserRefreshTokens(ctx, userID)

	// Delete user
	return s.repo.DeleteUser(ctx, userID)
}

// ListUsers retrieves users with filters
func (s *UserService) ListUsers(ctx context.Context, page, pageSize int, role, status, search string) ([]*models.User, int, error) {
	filters := bson.M{}

	if role != "" {
		filters["role"] = role
	}
	if status != "" {
		filters["status"] = status
	}
	if search != "" {
		filters["$or"] = []bson.M{
			{"username": bson.M{"$regex": search, "$options": "i"}},
			{"email": bson.M{"$regex": search, "$options": "i"}},
			{"fullName": bson.M{"$regex": search, "$options": "i"}},
		}
	}

	return s.repo.ListUsers(ctx, filters, page, pageSize)
}

// ChangePassword changes user's password
func (s *UserService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
	user, err := s.repo.GetUserByID(ctx, userID)
	if err != nil {
		return err
	}

	// Verify old password
	if err := auth.VerifyPassword(user.PasswordHash, oldPassword); err != nil {
		return fmt.Errorf("invalid old password")
	}

	// Hash new password
	newPasswordHash, err := auth.HashPassword(newPassword)
	if err != nil {
		return fmt.Errorf("failed to hash password: %w", err)
	}

	// Update password
	return s.repo.UpdatePassword(ctx, userID, newPasswordHash)
}

// ResetPassword resets user's password (simplified version)
func (s *UserService) ResetPassword(ctx context.Context, email string) (string, error) {
	user, err := s.repo.GetUserByEmail(ctx, email)
	if err != nil {
		// Don't reveal if email exists
		return "", nil
	}

	// In production, send email with reset link
	// For now, generate a simple reset token
	resetToken := uuid.New().String()

	// In production, store reset token with expiry
	// For this implementation, we'll just return the token
	_ = user // Use user variable

	return resetToken, nil
}

// AssignRole assigns a role to a user
func (s *UserService) AssignRole(ctx context.Context, userID string, role models.Role) (*models.User, error) {
	user, err := s.repo.GetUserByID(ctx, userID)
	if err != nil {
		return nil, err
	}

	user.Role = role

	if err := s.repo.UpdateUser(ctx, user); err != nil {
		return nil, err
	}

	return user, nil
}

// GetUserPermissions retrieves user's permissions
func (s *UserService) GetUserPermissions(ctx context.Context, userID string) ([]auth.Permission, models.Role, error) {
	user, err := s.repo.GetUserByID(ctx, userID)
	if err != nil {
		return nil, "", err
	}

	permissions := auth.GetPermissionsForRole(string(user.Role))
	return permissions, user.Role, nil
}

// saveRefreshToken saves a refresh token
func (s *UserService) saveRefreshToken(ctx context.Context, userID, token string) error {
	tokenHash := hashToken(token)

	refreshToken := &models.RefreshToken{
		ID:        uuid.New().String(),
		UserID:    userID,
		Token:     tokenHash,
		ExpiresAt: time.Now().Add(s.jwtManager.GetRefreshTokenExpiry()),
	}

	return s.repo.SaveRefreshToken(ctx, refreshToken)
}

// hashToken creates a SHA256 hash of a token
func hashToken(token string) string {
	hash := sha256.Sum256([]byte(token))
	return hex.EncodeToString(hash[:])
}
